package com.ruoyi.project.tugboat.service.impl;

import java.util.*;

import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.TypeReference;
import com.ruoyi.project.common.exception.CustomException;
import com.ruoyi.project.tugboat.domain.CTugboat;
import com.ruoyi.project.tugboat.domain.CDevice;
import com.ruoyi.project.tugboat.domain.CPoint;
import com.ruoyi.project.tugboat.domain.TMqttMsgConvert;
import com.ruoyi.project.tugboat.domain.vo.TMqttMsgDisplayVO;
import com.ruoyi.project.tugboat.mapper.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.ruoyi.project.tugboat.domain.TMqttMsg;
import com.ruoyi.project.tugboat.service.ITMqttMsgService;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;

/**
 * 【请填写功能名称】Service业务层处理
 * 
 * @author ruoyi
 * @date 2023-07-20
 */
@Service
public class TMqttMsgServiceImpl implements ITMqttMsgService 
{
    @Autowired
    private TMqttMsgMapper tMqttMsgMapper;

    @Autowired
    private TMqttMsgConvertMapper tMqttMsgConvertMapper;
    
    @Autowired
    private CTugboatMapper tugboatMapper;
    
    @Autowired
    private CDeviceMapper cDeviceMapper;
    
    @Autowired
    private CPointMapper cPointMapper;

    /**
     * 拖轮代码表（拖轮代码-拖轮名称）
     */
    private static final Map<String, String> tugboatMap = new HashMap<>();

    /**
     * 设备代码表（设备代码-设备名称）
     */
    private static final Map<String, String> deviceMap = new HashMap<>();

    /**
     * 测点代码表（测点代码-测点名称）
     */
    private static final Map<String, String> pointMap = new HashMap<>();

    /**
     * 构造数据缓存，将一些代码表中的数据加载到内存中，方便后续使用
     * 注意：1.该方法只会在系统启动时执行一次；2.该方法仅适用于代码表数据量较小的情况，如果数据量较大，建议使用redis等缓存数据库
     */
    @PostConstruct
    private void initCache() {
        
        List<CTugboat> tugboats = tugboatMapper.selectCTugboatList(new CTugboat());
        for (CTugboat cTugboat : tugboats) {
            tugboatMap.put(cTugboat.getTugboatCod(), cTugboat.getTugboatNam());
        }
        
        List<CDevice> devices = cDeviceMapper.selectCDeviceList(new CDevice());
        for (CDevice cDevice : devices) {
            deviceMap.put(cDevice.getDeviceCod(), cDevice.getDeviceNam());
        }
        
        List<CPoint> points = cPointMapper.selectCPointList(new CPoint());
        for (CPoint cPoint : points) {
            pointMap.put(cPoint.getPointCod(), cPoint.getPointNam());
        }
    }

    /**
     * 清除数据缓存，清空缓存中的数据
     */
    private void clearCache() {
        if (!tugboatMap.isEmpty()) {
            tugboatMap.clear();
        }
        if (!deviceMap.isEmpty()) {
            deviceMap.clear();
        }
        if (!pointMap.isEmpty()) {
            pointMap.clear();
        }
    }

    /**
     * 刷新数据缓存，清空缓存中的数据，并重新加载
     */
    private void refreshCache() {
        clearCache();
        initCache();
    }

    /**
     * 根据传入的消息载荷payload（为JSON格式字符串），构造返回结果集
     * @param payload 消息载荷
     * @return 返回结果集（Map<String, String>）
     */
    private static Map<String, String> getPayloadMap(String payload) {
        // 检测payload是否为json格式字符串，如果不是，返回null，表示转换失败
        if (!JSON.isValidObject(payload)) {
            return null;
        }
        // 转换为Map<String, String>格式的数据返回
        return JSON.parseObject(payload, new TypeReference<Map<String, String>>(){});
    }

    /**
     * 将原始数据表中的key替换为字典表中的value
     * @param dictMap 字典表
     * @param payloadMap 原始数据表
     * @param tipMsg 字典表未找到key时的提示语句
     * @return 返回结果集（Map<String, String>）
     */
    private static Map<String, String> payloadMapTranslate(Map<String, String> dictMap, Map<String, String> payloadMap, String tipMsg) {
        // 如果代码表或原始数据表为null，直接返回null
        if (dictMap == null || payloadMap == null) {
            return null;
        }

        // 遍历原始数据，将原始数据表中的key替换为字典表中的value，如果字典表中不存在该key，则将该key替换为提示语句，装入返回结果集result
        Map<String, String> result = new HashMap<>();
        for (String key : payloadMap.keySet()) {
            String value = dictMap.containsKey(key) ? payloadMap.get(key) : String.format("代码[%s]，%s", key, tipMsg);
            result.put(dictMap.get(key), value);
        }
        return result;
    }

    /**
     * 根据传入参数查询原始数据
     * @param tugboatId 拖轮代码
     * @param operType 操作类型
     * @param deviceId 设备代码
     * @param msgType 消息类型（switch 开关数据；simulating 模拟数据）
     * @param startTime 选取的开始时间
     * @param endTime 选取的结束时间
     * @return 返回结果集
     */
    public List<TMqttMsgDisplayVO> selectTMqttMsgListByParams(String tugboatId, String operType, String deviceId, String msgType, Date startTime, Date endTime) {
        
        final String TIP_MSG = "未找到对应的数据，请检查测点表是否已配置该代码";

        // 刷新缓存
        refreshCache();

        // 根据传入参数查询原始数据
        List<TMqttMsg> mqttMsgList = tMqttMsgMapper.selectTMqttMsgListByParams(tugboatId, operType, deviceId, msgType, startTime, endTime);
        List<TMqttMsgDisplayVO> vos = new ArrayList<>();
        // 此处是因为vo的所有字段都会被赋值，所以在此处new一个vo对象，避免每次循环创建对象的开销（如果vo的字段不全都会被赋值，那么就不能在此处new对象，否则会导致展示层数据混乱）
        TMqttMsgDisplayVO vo = new TMqttMsgDisplayVO();
        for (TMqttMsg tMqttMsg : mqttMsgList) {

            convertOriginalTMqttMsg(tMqttMsg);
            
            // 设置vo对象的字段值
            vo.setTMqttMsg(tMqttMsg);
            vo.setTugboatName(tugboatMap.get(tugboatId));
            vo.setOperName("monitoring".equals(operType) ? "监控" : "未知操作类型");
            vo.setDeviceName(deviceMap.get(deviceId));
            vo.setPointTypeName("switch".equals(msgType) ? "开关量" : "simulating".equals(msgType) ? "模拟量" : "未知数据类型");
            vo.setPointValueMap(payloadMapTranslate(pointMap, getPayloadMap(tMqttMsg.getPayload()), TIP_MSG));
            
            vos.add(vo);
        }
        
        return vos;
    }

    private void checkTugboatCod(String tugboatCod) {
        if (!tugboatMap.containsKey(tugboatCod)) {
            throw new CustomException(String.format("拖轮编码[%s]不存在", tugboatCod));
        }
    }
    
    private void checkDeviceCod(String deviceCod) {
        if (!deviceMap.containsKey(deviceCod)) {
            throw new CustomException(String.format("设备编码[%s]不存在", deviceCod));
        }
    }
    
    private void checkPointTyp(String pointTyp) {
        if (!"switch".equals(pointTyp) && !"simulating".equals(pointTyp)) {
            throw new CustomException(String.format("测点类型[%s]不存在", pointTyp));
        }
    }
    
    private boolean checkPointCod(String pointCod) {
        if (!pointMap.containsKey(pointCod)) {
            return false;
        }
        return true;
    }

    /**
     * 将原始的TMqttMsg转换为TMqttMsgConvert，并保存到数据库中，方便后续查询与数据展示
     * 根据对接标准，tMqttMsg的主题topic形如：tugboat/{tugboat_id}/{oper_type}/{device_id}/{msg_type}
     * 需要从中解析出tugboatCod(tugboat_id)、deviceCod(device_id)、pointTyp(msg_type)
     * @param tMqttMsg 原始的TMqttMsg对象
     */
    @Transactional
    public void convertOriginalTMqttMsg(TMqttMsg tMqttMsg) {
        
        // 校验原始MQTT消息是否为空
        if (tMqttMsg == null) {
            return;
        }

        // 校验原始MQTT消息的插入时间字段是否为空
        if (tMqttMsg.getInsTim() == null) {
            throw new CustomException("插入时间字段不能为空");
        }
        // 校验原始MQTT消息的ID字段是否为空
        if (tMqttMsg.getMsgid() == null) {
            throw new CustomException("原始MQTT消息的MSGID字段不能为空");
        }
        // 校验topic是否形如tugboat/{tugboat_id}/{oper_type}/{device_id}/{msg_type}
        if (!tMqttMsg.getTopic().matches("^tugboat/[\\w./-]+/[\\w./-]+/[\\w./-]+/[\\w./-]+$")) {
            throw new CustomException(
                    String.format("topic格式不正确，正确的格式为[%s]，请检查", "tugboat/{tugboat_id}/{oper_type}/{device_id}/{msg_type}")
            );
        }

        // 分割topic，获取tugboatCod、deviceCod、pointTyp
        List<String> topicList = Arrays.asList(tMqttMsg.getTopic().split("/"));
        String tugboatCod = topicList.get(1);
        String operTyp = topicList.get(2);
        String deviceCod = topicList.get(3);
        String pointTyp = topicList.get(4);

        // 校验tugboatCod、deviceCod、pointTyp是否正确，不存在/不正确则抛出异常
        checkTugboatCod(tugboatCod);
        checkDeviceCod(deviceCod);
        checkPointTyp(pointTyp);
        
        // 校验多个子字段拼接成的payload是否有内容缺失
        String payload = tMqttMsg.getPayload();
        if (!payload.contains("{") || !payload.contains("}")){
            throw new CustomException("数据库存储的payload内容有缺失");
        }
        
        // 将原始MQTT消息的payload转换为Map<String, String>对象
        Map<String, String> payloadMap = getPayloadMap(tMqttMsg.getPayload());
        if (payloadMap == null) {
            throw new CustomException("payload转换为Map对象失败");
        }
        // 校验payloadMap中的测点编码是否正确，不存在/不正确则抛出异常
        List <String> pointCodErrorList = new ArrayList<>();
        for (String key : payloadMap.keySet()) {
            if (!checkPointCod(key)) {
                pointCodErrorList.add(key);
            }
        }
        if (pointCodErrorList.size() > 0) {
            throw new CustomException(String.format("MQTT消息体中，下列测点编码[%s]不存在，请检查", String.join(",", pointCodErrorList)));
        }
        
        // 进行数据转换
        TMqttMsgConvert convert = new TMqttMsgConvert();
        convert.setMonitorTim(tMqttMsg.getInsTim());
        convert.setTugboatCod(tugboatCod);
        convert.setDeviceCod(deviceCod);
        convert.setPointTyp(pointTyp);
        convert.setMsgOriginId(tMqttMsg.getMsgid());
        // 设置测点编码、测点值，并逐条保存到数据库中（一条原始MQTT消息一般包含多个测点，因此是“一对多”的关系，需要利用for循环insert多次）
        for (String key : payloadMap.keySet()) {
            convert.setMsgConvertId(UUID.randomUUID().toString());
            convert.setPointCod(key);
            convert.setPointVal(payloadMap.get(key));
            tMqttMsgConvertMapper.insertTMqttMsgConvert(convert);
        }
        
    }
    
    /**
     * 查询【请填写功能名称】
     * 
     * @param msgid 【请填写功能名称】主键
     * @return 【请填写功能名称】
     */
    @Override
    public TMqttMsg selectTMqttMsgByMsgid(String msgid)
    {
        return tMqttMsgMapper.selectTMqttMsgByMsgid(msgid);
    }

    /**
     * 查询【请填写功能名称】列表
     * 
     * @param tMqttMsg 【请填写功能名称】
     * @return 【请填写功能名称】
     */
    @Override
    public List<TMqttMsg> selectTMqttMsgList(TMqttMsg tMqttMsg)
    {
        return tMqttMsgMapper.selectTMqttMsgList(tMqttMsg);
    }

    /**
     * 新增【请填写功能名称】
     * 
     * @param tMqttMsg 【请填写功能名称】
     * @return 结果
     */
    @Override
    public int insertTMqttMsg(TMqttMsg tMqttMsg)
    {
        return tMqttMsgMapper.insertTMqttMsg(tMqttMsg);
    }

    /**
     * 修改【请填写功能名称】
     * 
     * @param tMqttMsg 【请填写功能名称】
     * @return 结果
     */
    @Override
    public int updateTMqttMsg(TMqttMsg tMqttMsg)
    {
        return tMqttMsgMapper.updateTMqttMsg(tMqttMsg);
    }

    /**
     * 批量删除【请填写功能名称】
     * 
     * @param msgids 需要删除的【请填写功能名称】主键
     * @return 结果
     */
    @Override
    public int deleteTMqttMsgByMsgids(String[] msgids)
    {
        return tMqttMsgMapper.deleteTMqttMsgByMsgids(msgids);
    }

    /**
     * 删除【请填写功能名称】信息
     * 
     * @param msgid 【请填写功能名称】主键
     * @return 结果
     */
    @Override
    public int deleteTMqttMsgByMsgid(String msgid)
    {
        return tMqttMsgMapper.deleteTMqttMsgByMsgid(msgid);
    }
}
